


var monkeypuzzler = new function(){
	
	//utilities
	if(browserVersion.isIE)
	{
	    StringBuilder = function(str){ this.__buffer = str?[str]:[]; };
	    StringBuilder.prototype.append = function append(str){ this.__buffer.push(str); return this;};
	    StringBuilder.prototype.toString = function toString(){ return this.__buffer.join(''); };
	}
	else
	{
	    StringBuilder = function(str) { this.__string = str?str:''; };
	    StringBuilder.prototype.append = function append(str){this.__string += str.toString(); return this;};//faster in moz
	    StringBuilder.prototype.toString = function append(str){ return this.__string; };
	};
	
	//style based utilities and constants
	/**
	 * Lookup table for border widths. Not strictly necessary, but it does reduce typos. 
	 */
	var BORDER_WIDTHS = {
		top : 'borderTopWidth',
		right : 'borderRightWidth',
		bottom : 'borderBottomWidth',
		left : 'borderLeftWidth'
	};
	/**
	 * Finds the current string value of a property on a given elements current style in a cross browser manner
	 * @param {HTMLElement} element
	 * @param {String} prop
	 * @return {String, null} for the most part. Haven't done full testing.
	 */
	function getStyleProperty(element, prop)
	{
		var value = null;
	    if (document.defaultView && document.defaultView.getComputedStyle)
		{
	        var css = document.defaultView.getComputedStyle(element, null);
	        value = css ? css[prop] : null;
	    }
		else if (element.currentStyle) 
		{
	        value = element.currentStyle[prop];
	    }
		else if (element.style[prop])
		{
			value = element.style[prop]; //end of the line. Got some work to do, in order to manage opera and safari
		}
		return value == 'auto' ? null : value;
	}
	/**
	 * Finds the current numeric value of a property on a given elements current style in a cross browser manner.
	 * @param {Object} element
	 * @param {Object} prop
	 * @return {Number, null}
	 */
	function getNumericStyleProperty(element, prop)
	{
		var raw = String(getStyleProperty(element, prop)).replace(/px/gi, ''),
			value = Number(raw);
		if (isNaN(value))
			return 0;
		return value;
	}
	
	
	if (window.netscape) //check for the Gecko rendering engine. Please note that browserVersion fails me when looking for seamonkey.
	{
		function getColorStyleValue(element, style)
		{
			var raw = getStyleProperty(element, style);
			if (!raw || !raw.length || raw == "transparent") 
				return null;
			var matches = (raw.match(/rgb\((\d+),\s(\d+),\s(\d+)\)/i));
			return [Number(matches[1]),Number(matches[2]),Number(matches[3])];
		}
		
		
		/**
		 * @param {HTMLElement} element
		 * @param {String} border One of the following values: [top, right, bottom, left]
		 * @return {Number, null} the width of the section of the border
		 */
		function getBorderWidth(element, border)
		{
			return getNumericStyleProperty(element, BORDER_WIDTHS[border]);
		}
		
	}
	else 
		if (browserVersion.isIE) 
		{
			/**
			 * A delightful function that attempts to convert a value in em,pt,% to the actual pixel size. 
			 * Shoot me.
			 * @param {HTMLElement} element
			 * @param {String} value
			 * @return {Number, null} The pixel size.
			 */
			function cssUnitToPx(element, value)
			{
				//em.
				//the default font size is 16px, would should relate to 12pt
			}
			
			function getColorStyleValue(element, style)
			{
				var raw = getStyleProperty(element, style);
				if (!raw || !raw.length || raw.charAt(0)!= '#') 
					return null;
				
				var hex = null;
				if (raw.length == 4) 
				{
					var shortHex = raw.substr(1).split('');
					hex = [shortHex[0] + shortHex[0], shortHex[1] + shortHex[1], shortHex[2] + shortHex[2]];
				}
				else 
					hex = raw.substr(1).match(/([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})/i).slice(1);
				return [Number('0x'+hex[0]),Number('0x'+hex[1]),Number('0x'+hex[2])];					
				
			}
			function getBorderWidth(element, border)
			{
				var raw = getStyleProperty(element, BORDER_WIDTHS[border]);
				if (raw == '')
					raw = getStyleProperty(element, 'borderWidth');
				
				return num;
			}
		}

	// for the best. There are several strategies for getting attributes, but this is the most reliable.
	// all properties here are either useless, or require special handling
	var ignorableProperties = {
		outerHTML : true,
		outerText : true,
		innerText : true,
		id:true,
		tagName : true,
		innerHTML : true,
		offsetHeight:true,
		offsetWidth:true,
		nodeName:true,
		localName:true,
		ELEMENT_NODE:true,
		ATTRIBUTE_NODE:true,
		TEXT_NODE:true,
		CDATA_SECTION_NODE:true,
		ENTITY_REFERENCE_NODE:true,
		ENTITY_NODE:true,
		PROCESSING_INSTRUCTION_NODE:true,
		COMMENT_NODE:true,
		DOCUMENT_NODE:true,
		DOCUMENT_TYPE_NODE:true,
		DOCUMENT_FRAGMENT_NODE:true,
		NOTATION_NODE:true,
		offsetTop:true,
		offsetLeft:true,
		scrollTop:true,
		scrollLeft:true,
		scrollHeight:true,
		scrollWidth:true,
		clientHeight:true,
		clientWidth:true,
		tabIndex:true,
		baseURI:true,
		baseURN:true,
		textContent:true,
		DOCUMENT_POSITION_DISCONNECTED:true,
		DOCUMENT_POSITION_PRECEDING:true,
		DOCUMENT_POSITION_FOLLOWING:true,
		DOCUMENT_POSITION_CONTAINS:true,
		DOCUMENT_POSITION_CONTAINED_BY:true,
		DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC:true
	};
	var serializableTypes = {
		'String' : true,
		'string' : true,
		'Number' : true,
		'number' : true
	};
	function serializeAttributes(element)
	{
		attr = {};
		for (var prop in element) 
			if (!(prop in ignorableProperties) && (typeof element[prop]) in serializableTypes) 
			{
				try 
				{
					attr[prop] = String(element[prop]);
				} 
				catch (e) 
				{
				//IE's version of "don't touch"
				}
			}
		return attr;
	}
	
	
	var styleProperties = {
		'color' : getColorStyleValue,
		'backgroundColor' : getColorStyleValue,
		'paddingLeft' : getNumericStyleProperty,
		'paddingRight': getNumericStyleProperty,
		'paddingTop': getNumericStyleProperty,
		'paddingBottom':getNumericStyleProperty,
		'borderColor':getColorStyleValue,
		'position': getStyleProperty
	};
	
	
	function serializeStyle(element)
	{
		var style = {};
		
		for (var prop in styleProperties)
			if (styleProperties.hasOwnProperty(prop))
				style[prop] = styleProperties[prop](element, prop);
		//console.debug('style %o',style);
		return style;
	}
	
	
	
	/**
	 * @param {HTMLElement} element
	 * @return {Number} the height of the element, without padding or margin
	 */
	function getElementHeight(element)
	{
		var rawHeight = element.offsetHeight || 0;
		if (element.tagName) 
		{
			rawHeight -= getNumericStyleProperty(element, 'paddingTop');
			rawHeight -= getNumericStyleProperty(element, 'paddingBottom');
		}
		
		return rawHeight;
	}
	/**
	 * @param {HTMLElement} element
	 * @return {Number} the width of the element, without padding or margin
	 */
	function getElementWidth(element)
	{
		var rawWidth = element.offsetWidth || 0;
		if (element.tagName) 
		{
			rawWidth -= getNumericStyleProperty(element, 'paddingLeft');
			rawWidth -= getNumericStyleProperty(element, 'paddingRight');
		}
		return rawWidth;
	}
	
	
	function Point(x,y)
	{
		this.x = x;
		this.y = y;
	}
	
	function Bounds(x1,y1,x2,y2)
	{
		this.x1 = x1;
		this.y1 = y1;
		this.x2 = x2;
		this.y2 = y2;
	}
	
	function DimensionElement(element)
	{
		this.top = getElementPosition(element);
		this.bottom = new Point( this.top.x + getElementWidth(element), this.top.y + getElementHeight(element) );
	}
	/**
	 * Returns a dimension object with the 
	 * @param {Object} dimEle
	 */
	DimensionElement.prototype.contains = function contains(dimEle)
	{
		if (dimEle.x1 >= this.x1 && dimEle.x1 <= this.x2)
		{}
	};
	
	/**
	 * @param {HTMLElement} element
	 */
	function getElementPosition(element)
	{
		var p = new Point(0,0),
			current = element;
		while(current)
		{
			p.x += current.offsetLeft;
			p.y += current.offsetTop;
			current = current.offsetParent;
		}
		return p;
	}
	
	/**
	 * My, that's a nice set of properties on that element. I just want to take it and serialize it like there is no tomorrow.
	 * @param {HTMLElement} element
	 */
	function objectifyElement(element)
	{
		var obj = {};
		obj.position = getElementPosition(element);
		obj.tagName = String(element.tagName).toLowerCase();
		obj.id = element.id;
		obj.height = getElementHeight(element);
		obj.width = getElementWidth(element);
		obj.attributes = serializeAttributes(element);
		obj.style = serializeStyle(element);
		return obj;
	}
	
	/**
	 * @param {Array} a
	 */
	function jsonArray(a)
	{
		var json = new StringBuilder('[');
		
		if (a.length)
			json.append('"');
			
		json.append(a.join('","'));
		
		if (a.length)
			json.append('"');
			
		json.append(']');
		return json.toString();
	}
	
	
	
	/**
	 * @param {Object} m
	 */
	function jsonMap(m)
	{
		var json = new StringBuilder('{'),
			i = [];
		
		for (var key in m) 
		{
			if (! m.hasOwnProperty(key)) //Grrrr.... I really want Object.notEnumerable();
				continue;
			var keyval = new StringBuilder(),
				type = (typeof m[key]).toLowerCase();
			i.push(keyval);
			
			keyval.append(key).append(':');
			
			
			switch(type)
			{
				case 'array':
					keyval.append(jsonArray(m[key]));
					break;
				
				case 'object':
					if (m[key] && !(m[key] instanceof Number || m[key] instanceof String)) 
					{//null is an object, so watch for it
						keyval.append(jsonMap(m[key]));
						break;
					}
				case 'number':
					keyval.append(String(m[key]));
					break;
				case 'null':
				case 'boolean':
				case 'undefined':
				case 'string':
					keyval.append('"').append(String(m[key])).append('"');
					break;
				default:
					keyval.append('"not serialized').append().append('"');
			}
		}		
		json.append(i.join(',')).append('}');
		
		return json.toString();
		
	}
	
	PageBot.prototype.returnJsonFromLocator = function(locator)
	{
		var element = this.findElement(locator);
		if (element)
			return jsonMap(objectifyElement(element));
	};
	
	//export the functions for testing.
	return {
		getColorStyleValue : getColorStyleValue,
		getStyleProperty : getStyleProperty,
		getNumericStyleProperty : getNumericStyleProperty,
		getElementWidth : getElementWidth,
		getElementHeight : getElementHeight,
		getBorderWidth : getBorderWidth
	}
};